feat: simplify agent booking flow — pre-resolution, tool filtering, Redis persistence#32
feat: simplify agent booking flow — pre-resolution, tool filtering, Redis persistence#32dhairyashiil wants to merge 14 commits intomainfrom
Conversation
- Rewrite booking flow in system prompt to clarification-first logic
with fast-path for fully-specified requests
- Add guestEmails field to book_meeting tool for email-only additional
attendees (Strategy A: guests array in initial POST)
- Add add_booking_attendee tool for full attendee records with name +
timezone after booking (Strategy B: POST /bookings/{uid}/attendees)
- Add AddAttendeeInput type and addBookingAttendee() client function
- Add guests field to CreateBookingInput type
- Relax CRITICAL RULES to support multi-step booking flows and
re-calling check_availability for alternative slots
- Update check_availability description to allow re-calls
- Bump MAX_STEPS from 10 to 15 for multi-attendee flows
- Update Available Capabilities to list add_booking_attendee
|
Deployment failed with the following error: View Documentation: https://vercel.com/docs/accounts/team-members-and-roles |
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
…ring, and Redis persistence - Remove check_account_linked tool (bot.ts already checks before agent) - Pre-resolve Slack @mentions to name+email before calling agent - Inject linked user context (email/timezone/username) into system prompt - Rewrite system prompt with checklist-based single-decision booking - Persist tool results in Redis (keyed by threadId, 30min TTL) - Context-aware tool filtering: CORE (7 booking tools) vs EXTENDED (admin) - Add loop guard: force text if same tool called 3x with identical args - Reduce MAX_STEPS from 15 to 8 (agent needs 3-4 steps with optimizations)
|
Deployment failed with the following error: View Documentation: https://vercel.com/docs/accounts/team-members-and-roles |
There was a problem hiding this comment.
3 issues found across 7 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/chat/slack-manifest.yml">
<violation number="1" location="apps/chat/slack-manifest.yml:37">
P1: This callback URL no longer matches the app's canonical base URL, so Slack OAuth installs can fail with `bad_redirect_uri`.</violation>
</file>
<file name="apps/chat/lib/user-linking.ts">
<violation number="1" location="apps/chat/lib/user-linking.ts:291">
P1: Encrypt persisted tool-context entries before writing them to Redis.</violation>
</file>
<file name="apps/chat/lib/bot.ts">
<violation number="1" location="apps/chat/lib/bot.ts:719">
P1: The injected tool context is immediately removed by `slice(0, -1)`. The slice was meant to remove the duplicate current user message, but now it removes the tool context that was just appended. Either slice the history before injecting tool context, or inject the tool context at a different position.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
…ndling, tool context reuse - Replace bare ISO timestamp with timezone-aware date in system prompt - Add explicit past-date comparison rule against user's timezone - Lower loop guard threshold from 3 to 2 identical calls - Reduce MAX_STEPS from 8 to 6 - Rewrite tool context injection: user role, deduplicate by tool name, inject at start of history - Add ASAP/urgency shortcut section to booking flow - Add duration mismatch detection rule - Strengthen tool usage rules (no same tool twice per step, must use returned data) - Add single event type auto-select logic
…me tool - Add getEventTypesByUsername() to calcom/client.ts (public API, no auth needed) - Add list_event_types_by_username tool to agent createCalTools - Add list_event_types_by_username to CORE_TOOL_NAMES - Add WHOSE CALENDAR TO USE section to system prompt booking flow
…ld support Fix 1: Updated getCustomErrorMessage in all 3 handlers to return generic error when lastStreamErrorRef is set and caught error is Slack auth error. Fix 2a: Added responses?: Record<string, unknown> to CreateBookingInput and CreatePublicBookingInput types. Fix 2b: Added responses parameter to book_meeting and book_meeting_public tool schemas, passed through to API calls. Fix 2c: Included bookingFields in list_event_types_by_username response. Fix 2d: Added CUSTOM BOOKING FIELDS section to system prompt instructing agent to collect required custom field values and pass as responses. Fix 3: Hardened postAgentStream to re-throw generic error when Slack auth error is secondary to an agent stream failure. Also fixed duplicate rule #6 numbering in CRITICAL RULES.
There was a problem hiding this comment.
1 issue found across 4 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/chat/lib/calcom/types.ts">
<violation number="1" location="apps/chat/lib/calcom/types.ts:91">
P1: Add `metadata` to `CreatePublicBookingInput`; otherwise bookings made through the public/attendee-calendar flow cannot carry the Slack or Telegram routing IDs that the webhook handler uses for notifications.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
2 issues found across 7 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/chat/slack-manifest.yml">
<violation number="1" location="apps/chat/slack-manifest.yml:30">
P1: Don't hardcode the Slack manifest to a deployment-specific Vercel URL; it can break OAuth and all Slack callbacks when that deployment changes.</violation>
</file>
<file name="apps/chat/lib/calcom/types.ts">
<violation number="1" location="apps/chat/lib/calcom/types.ts:91">
P2: `CreatePublicBookingInput` is missing the metadata field needed to route webhook notifications for bookings made on another user's calendar.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
…cation routing 1. Encrypt tool-context entries in Redis using existing encryptData/decryptData (PII protection for attendee names, emails, booking details). Legacy plaintext entries are handled gracefully by decryptData. 2. Added metadata?: Record<string, string> to CreatePublicBookingInput. 3. Added platform parameter to createCalTools, threaded from runAgentStream. 4. Both book_meeting and book_meeting_public now pass metadata with slack_team_id/slack_user_id or telegram_chat_id so Cal.com webhooks can route notifications back to the correct Slack/Telegram user.
|
@cubic-dev-ai Your task is to perform a comprehensive code review
|
@dhairyashiil I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
1 issue found across 6 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/chat/lib/calcom/client.ts">
<violation number="1" location="apps/chat/lib/calcom/client.ts:137">
P1: Request `format=range` for public slot lookups before reading `s.start`; otherwise the public availability flow can normalize invalid slot times.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
feat: simplify agent booking flow — pre-resolution, tool filtering, Redis persistence
Summary
Six phases of changes to the AI agent's booking flow:
Phase 1: Smart @mention booking flow with multi-attendee support
Upgrades the booking flow so
@Cal.com book a meeting with @bailey and @Keith Williamsworks end-to-end on Slack and Telegram — with clarification-first logic, multi-attendee resolution, and a fast-path for fully-specified requests.Tool & API changes (
lib/agent.ts,lib/calcom/client.ts,lib/calcom/types.ts):book_meeting— adds optionalguestEmails: string[]field, passed asguestsin thePOST /v2/bookingspayload (Strategy A: email-only additional attendees)add_booking_attendee(new) — callsPOST /v2/bookings/{uid}/attendeesto add a full attendee record after booking (Strategy B: resolved Slack @mentions with name + timezone)CreateBookingInput— addsguests?: string[]AddAttendeeInput(new type) +addBookingAttendee()client functionPhase 2: Architectural simplification (inspired by Gemini CLI patterns)
Eliminates unnecessary tool calls by moving auth checks and user resolution out of the AI layer, persisting tool results across turns, and adding context-aware tool filtering.
Changes in
lib/agent.ts:check_account_linkedtool —bot.tsalready callsgetLinkedUser()before invoking the agent; every tool'sexecutealready checksgetAccessTokenOrNull(). Redundant AI-decided call eliminated.UserContextinterface (email, username, timezone) passed from bot layer. Eliminates need for AI to callget_my_profilefor basic info.list_event_types,list_event_types_by_username,check_availability,book_meeting,add_booking_attendee,list_bookings,get_booking,cancel_booking) and EXTENDED (admin tools like schedule/event type management, profile, etc.). Intent detector selects which set based on message keywords.lookup_platform_userandget_my_profilemoved to EXTENDED.prepareStepcallback tracks tool calls across steps; forces text response if same tool called with identical arguments (threshold: 2).Changes in
lib/bot.ts:preResolveMentions()— before calling agent, extracts all<@USER_ID>patterns, resolves them via Slack API in parallel, returns{ text, attendees }. The text has a[Context: @mentions resolved]block prepended; the attendees array is persisted to Redis for cross-turn reuse.buildUserContext()— converts linked user data toUserContextfor system prompt injection.persistBookingContext()— detects ASAP intent via regex, persists_booking_intentand_resolved_attendeesas tool context entries in Redis so they appear in[CACHED TOOL DATA]on subsequent turns.buildEnrichedMessage()— reads tool results from Redis, deduplicates by tool name (keeping latest), and prepends[CACHED TOOL DATA]directly into the user message so the model treats it as part of the current request.postAgentStream()tool persistence — after each agent run, extracts tool results from the stream's steps and writes them to Redis (merged with existing, capped at 10 entries).reconstructSlackMentions()+normalizeSlackText()used inonNewMentionandonSubscribedMessagehandlers.onNewMention,onSubscribedMessage) updated to builduserContext, enrich messages with cached data, detect tool set, and pre-resolve mentions.Changes in
lib/user-linking.ts:ToolContextEntryinterface +getToolContext()/setToolContext()— Redis-backed tool result persistence keyed bycalcom:tool_context:{threadId}with 30-minute TTL.Phase 3: Booking flow bug fixes (log-informed)
Fixes tool call looping, "in the past" hallucination, ASAP handling, and reduces round-trips — root causes identified from server logs of real conversations.
Changes in
lib/agent.ts:Current date/time (your timezone, Asia/Kolkata): Thursday, March 13, 2026, 6:49 PM IST). Adds explicit rule: "A date is 'in the past' ONLY if it has already passed in the user's timezone." Eliminates the hallucinated "in the past" error caused by UTC-only timestamps.list_event_typesbefore the guard fired._booking_intentin Redis means follow-up messages ("use the 15 min one") honor the original ASAP intent.check_availabilityzero-slots handling — when no slots found in the requested range, auto-searches next 14 days for alternatives. Returns structured{ totalSlots: 0, noSlotsReason, nextAvailableSlots }with weekend detection and clear instructions to the model..passthrough()on empty zod schemas — prevents tool call rejections when LLM sends unexpected extra properties in args for no-parameter tools.isAIToolCallError— catches"tool call validation failed"and"which was not in request.tools"(relevant with CORE/EXTENDED tool filtering).Changes in
lib/bot.ts:withSlackToken()helper — wraps async operations with Slack bot token explicitly set in context, preventingnot_authederrors whenAsyncLocalStoragecontext is lost across async boundaries (VercelwaitUntil, long-running agent streams).promptUserToLink()helper — extracted duplicated OAuth link prompt logic fromonNewMentionandonSubscribedMessageinto single reusable function. Supports"expired"reason with different card title.getValidAccessToken()aftergetLinkedUser()and show "session expired" card if token is revoked.safePostwrappers — error posting inonNewMentionandonSubscribedMessagewrapped withwithSlackTokento prevent auth context loss.Phase 4: Attendee calendar booking flow
Adds a "book on attendee's calendar" option. When a user mentions someone and wants to book, the bot can now ask whose event types to use — the host's (current behavior) or the attendee's (new). If the attendee's, their public Cal.com event types are fetched by username.
Changes in
lib/calcom/client.ts:getEventTypesByUsername()(new) — callsGET /v2/event-types?username=<username>with only thecal-api-version: 2024-06-14header (no auth token required). Returns the sameCalcomEventType[]shape as the authenticatedgetEventTypes().getAvailableSlotsPublic()(new) — callsGET /v2/slotswitheventTypeSlug+usernamequery params,cal-api-version: 2024-09-04. No auth required.createBookingPublic()(new) — unauthenticatedPOST /v2/bookingswitheventTypeSlug+username. Enhanced error parsing from response body.Changes in
lib/agent.ts:list_event_types_by_username(new tool) — wrapsgetEventTypesByUsername(). Returns event types with id, title, slug, duration, description, hidden, bookingUrl, and bookingFields.check_availability_public(new tool) — wrapsgetAvailableSlotsPublic(). Same zero-slots handling as authenticated version.book_meeting_public(new tool) — wrapscreateBookingPublic(). TakeseventTypeSlug+usernameinstead ofeventTypeId.CORE_TOOL_NAMES— available in the core booking flow.check_availabilityfor another user's event type — usecheck_availability_publicinstead."Changes in
lib/calcom/types.ts:CreatePublicBookingInput— new type witheventTypeSlug,username,start,attendee,guests?,notes?,lengthInMinutes?.Phase 5: Fix false-positive Slack auth error + custom booking field support
Fixes a bug where the "Slack app token has expired" error was a false positive triggered when the AI agent's streaming response fails mid-flight (e.g. due to a Cal.com API booking error for missing custom fields), and the resulting error is misclassified as a Slack auth error in the error handling chain. Also adds
responsesfield support for custom booking fields.Root cause: When the agent stream fails (e.g., Cal.com returns 400 for missing required booking fields), the error can cause
AsyncLocalStoragecontext loss on Vercel's serverless runtime. Subsequent Slack API calls then fail withnot_authed, whichwithBotErrorHandlingclassifies as "Slack app token has expired" — masking the real error.Changes in
lib/bot.ts:getCustomErrorMessageupdated in all 3 handlers — now accepts the caughterrparameter. WhenlastStreamErrorRef.currentis set (meaning the agent stream had an error) and the caught error is a Slack auth error, returns a generic "Sorry, something went wrong" message instead of the misleading "token expired" message.postAgentStreamhardened — catch block now checks if the thrown error is a Slack auth error AND the agent stream had captured errors (viaonErrorRef.current). If so, re-throws as a genericErrorwith the original stream error message, preventing the Slack auth error from propagating towithBotErrorHandling.Changes in
lib/calcom/types.ts:responses?: Record<string, unknown>added to bothCreateBookingInputandCreatePublicBookingInput— allows passing custom booking field values to the Cal.com API.Changes in
lib/agent.ts:responsesparameter added to bothbook_meetingandbook_meeting_publictool schemas (z.record(z.string(), z.unknown())), passed through tocreateBooking()andcreateBookingPublic()calls.bookingFieldsincluded inlist_event_types_by_usernametool response — the agent can now see which custom fields an event type requires.required: truebooking fields, collect values from the user, and pass them asresponsesin the booking call.!) with nullish coalescing (?? "") incheck_availability_publicto satisfy biomenoNonNullAssertionrule.Phase 6: Encrypt tool context + webhook notification routing metadata
Addresses PR review feedback: encrypts PII in Redis tool-context entries and adds metadata to booking API calls so Cal.com webhooks can route notifications back to the correct Slack/Telegram user.
Changes in
lib/user-linking.ts:setToolContextnow wrapsJSON.stringify(entries)withencryptData()before writing to Redis. Tool context can contain PII (attendee names, emails from resolved mentions, booking details).getToolContextnow wraps the raw Redis value withdecryptData()beforeJSON.parse. Legacy plaintext entries are handled gracefully bydecryptData(the"enc:"prefix check at line 33).Changes in
lib/calcom/types.ts:metadata?: Record<string, string>added toCreatePublicBookingInput— was already present onCreateBookingInput.Changes in
lib/agent.ts:platformparameter added tocreateCalTools()function signature, threaded fromrunAgentStream().book_meetingexecute now builds and passesmetadatawithslack_team_id/slack_user_id(for Slack) ortelegram_chat_id(for Telegram). The webhook handler atapp/api/webhooks/calcom/route.tsuses these fields to route booking notifications to the correct channel/DM.book_meeting_publicexecute — same metadata logic applied.Why this matters: Previously, bookings created via the bot never included routing metadata, so Cal.com webhooks had no way to deliver Slack notifications for those bookings (the handler checks
metadata.slack_team_idat line 61-63 and skips without it). Telegram had a partial fallback via organizer email lookup, but Slack had none.Expected impact on tool call count:
responses)Review & Testing Checklist for Human
metadataobject (slack_team_id,slack_user_id) must match what the webhook handler atapp/api/webhooks/calcom/route.tsexpects. This was completely broken before — bookings created via the bot never included routing metadata.bookingFieldsshape from public API: Thelist_event_types_by_usernametool now returnsbookingFields. Verify that the publicGET /v2/event-types?username=endpoint returnsbookingFieldswithname,type, andrequiredproperties as the prompt assumes.responsesfield format accepted by Cal.com API: Theresponsesobject uses the field'sname(slug) as the key. ConfirmPOST /v2/bookingsexpects slug-keyed responses (some APIs useidinstead).list_event_typeslooping on turn 1, (2) turn 2 uses cached event types and honors ASAP intent, (3) turn 3 doesn't hallucinate "in the past".responsesinbook_meeting_public.decryptDatafunction returns plaintext as-is when there's noenc:prefix). New entries should be encrypted.POST /v2/bookings/{uid}/attendeesendpoint exists in the Cal.com v2 API and accepts{ name, email, timeZone }.Recommended test plan
Notes
ai/chat/zodmodule declarations when not installed) are unchanged. No new type errors introduced — CI typecheck passes.postAgentStream()usesRecord<string, unknown>casts to extract from the AI SDK'sunknown[]steps type. This is intentional to avoid coupling to the SDK's complex genericStepResulttype, with runtime guards (typeof r.toolName === "string") for safety.check_account_linkedtool removal is safe becausebot.tsalready gates ongetLinkedUser()in all three handlers before invoking the agent. But if any new entry point is added that skips this check, there's no fallback.buildEnrichedMessageapproach (prepending[CACHED TOOL DATA]directly into the user message text) is a deliberate change from the earlierinjectToolContextapproach (injecting as a separate message in conversation history). The earlier approach was being ignored by the model. This approach makes it impossible to ignore but means the cached data is part of the "user said" content, which could have unexpected effects on model behavior.getEventTypesByUsername()function makes an unauthenticated API call. There is no rate limiting or caching implemented — if the model calls it repeatedly (e.g., due to a prompt misunderstanding), it could hit Cal.com's public API rate limits.postAgentStreamhardening (Fix 3) depends on timing:onErrorRef.currentmust be set by the agent stream error handler before the Slacknot_authederror is thrown. In practice this should always hold because the stream error fires first and the Slack error is a consequence, but this is worth monitoring in logs.book_meetingandbook_meeting_publicnow always pass ametadataobject (empty{}for unknown platforms). If Cal.com treats empty metadata differently from absent metadata, this could have side effects — worth verifying.Link to Devin Session: https://app.devin.ai/sessions/f69e6c70ee544da7b5b6e91a9b73d29b
Requested by: @dhairyashiil